package net.zoneland.knowledge.filters;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Lists;

import net.zoneland.knowledge.constant.Constants;
import net.zoneland.knowledge.model.LoginUserPojo;
import net.zoneland.knowledge.model.ResponseResult;
import net.zoneland.knowledge.service.AuthService;
import net.zoneland.knowledge.service.UserService;
import net.zoneland.knowledge.utils.GsonUtils;
import net.zoneland.knowledge.utils.JwtUtil;
import net.zoneland.knowledge.utils.LoginUtils;

/**
 * token验证.
 * 对前端请求验证token.
 * 数据同步等接口不需要验证token
 *
 * @author: bcc
 * 2021/5/28
 */
public class TokenFilter implements Filter {

    /**
     * content-Type.
     */
    public static final String CONTENT_TYPE = "application/json; charset=utf-8";

    /**
     * 匿名访问资源.
     */
    protected static final List<String> ALLOWED_URL =  Collections.unmodifiableList(
            Lists.newArrayList(
                    "/login.jsp",
                    "/login",
                    "/health",
                    "/datamanage/*",
                    "/qa/search4ds",
                    "/qa/querybyid4ds",
                    "/qa/querybypath4ds",
                    "/ua/cg/*",
                    "/healthcheck",
                    "/ua/getUserByTime",
                    "/ua/getOrgByTime",
                    "/diYiYiTi/*",
                    "/sanKe/*",
                    "/oplog/*",
                    "/directTrainForParty/*",
                    "/preview/downloadFile",
                    "/preview/previewInfo",
                    "/sanKe/downloadGet",
                    "/partycalendartoday/*",
                    "/documentVerification/termManagement/*",
                    "/documentVerification/*",
                    "/api/secretary/*",
                    "/workReminder/reminders-content-export"
            )
    );

    /**
     * 定义匿名访问的URI.
     */
    private static final ImmutableMultiset<String> ANONYMOUS_URI = ImmutableMultiset.of(
            "/api/downloadFile.ajax/file.file",
            "/api/work/downloadAttachment",
            "/api/chapter/downloadApproveForm",
            "/sanKe/download",
            "/sanKe/downloadGet",
            "/qa/search4ds",
            "/qa/querybyid4ds",
            "/qa/querybypath4ds",
            "/preview/downloadFile",
            "/preview/previewInfo",
            "/api/secretary/downloadSecretaryForm"
    );
    /**
     * logger.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(TokenFilter.class);
    /**
     * ENCODE_UTF8.
     */
    private static final String ENCODE_UTF8 = "UTF-8";
    /**
     * token认证失败提示语.
     */
    private static final String TOKEN_VERIFY_ERROR_MSG = "token认证失败！";
    /**
     * 待拦截地址uri正则.
     */
    private static final Pattern FILTER_URI_PATTERN = Pattern.compile("^/(api|qa|ec|ts|he|qr|footprint|preview)/");
    /**
     * 用户业务接口.
     */
    private UserService userService;
    /**
     * 认证业务接口.
     */
    private AuthService authService;

    /**
     * .
     *
     * @param filterConfig 配置
     * @throws ServletException 异常
     */
    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        // do nothing
    }

    /**
     * 销毁.
     */
    @Override
    public void destroy() {
        // do nothing
    }

    /**
     * token拦截认证.
     *
     * @param request  http请求
     * @param response http响应
     * @param chain    过滤链
     * @throws IOException      IO异常
     * @throws ServletException web异常
     */
    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
            throws IOException, ServletException {
        if (TokenFilter.LOGGER.isDebugEnabled()) {
            TokenFilter.LOGGER.debug("do LoginFilter");
        }
        final HttpServletRequest httpRequest = (HttpServletRequest) request;
        //如果是匿名可访问URI或者是不需要验证token的URL，则继续后续的过滤操作
        if (!needCheckToken(httpRequest)) {
            chain.doFilter(request, response);
            return;
        }

        final String token = httpRequest.getHeader(Constants.TOKEN_NAME);
        //验证token不通过，则直接返回前端异常结果
        if (StringUtils.isBlank(token) || !JwtUtil.verify(token, JwtUtil.getTokenId(token))) {
            ((HttpServletRequest) request).getSession().removeAttribute(Constants.OPERATION_ATTR);
            sendMsgToResponse(response, TOKEN_VERIFY_ERROR_MSG, ResponseResult.ACCESS_FAILED_RESULT);
            LOGGER.error("========TokenFilter doFilter failed! token is null!,url:{}",
                httpRequest.getRequestURL());
            return;
        }

        //根据token获取当前登录人信息
        final LoginUserPojo user = LoginUtils.getCurrentUserByToken(token);
        if (user == null) {
            //根据token获取不到用户信息，则将当前token移除session，返回前端异常结果
            ((HttpServletRequest) request).getSession().removeAttribute(Constants.OPERATION_ATTR);
            sendMsgToResponse(response, TOKEN_VERIFY_ERROR_MSG, ResponseResult.ACCESS_FAILED_RESULT);
            LOGGER.error("========TokenFilter LoginUtils.getCurrentUserByToken null! token:{}!", token);
        } else if (StringUtils.isEmpty(userService.getCityId(user))) {
            sendMsgToResponse(response, "人员组织DN错误，无法找到cityId", ResponseResult.FAILED_RESULT);
        } else {
            chain.doFilter(request, response);
        }
    }

    /**
     * 判断是否为需要验证token.
     * @param request http请求
     * @return 是/否
     */
    private boolean needCheckToken(final HttpServletRequest request) {
        if (authService.isMock()) {
            return false;
        }
        final String servletPath = Optional.ofNullable(request.getServletPath())
            .filter(StringUtils::isNotBlank)
            .orElseGet(() -> StringUtils.replace(request.getRequestURI(), request.getContextPath(), StringUtils.EMPTY));
        LOGGER.info("ANONYMOUS_URI= {}", ANONYMOUS_URI.contains(servletPath));
        LOGGER.info("FILTER_URI_PATTERN= {}", TokenFilter.FILTER_URI_PATTERN.matcher(servletPath).find());
        return !ANONYMOUS_URI.contains(servletPath)
            && TokenFilter.FILTER_URI_PATTERN.matcher(servletPath).find();
    }

    /**
     * 设置响应到response.
     *
     * @param response   http响应
     * @param msg        内容
     * @param resultCode 错误码
     * @throws IOException 异常
     */
    private static void sendMsgToResponse(final ServletResponse response, final String msg, final String resultCode)
            throws IOException {
        response.setCharacterEncoding(TokenFilter.ENCODE_UTF8);
        response.setContentType(TokenFilter.CONTENT_TYPE);
        final PrintWriter out = response.getWriter();
        final ResponseResult responseResult = new ResponseResult();
        responseResult.setResult(resultCode);
        responseResult.setMessage(msg);
        out.append(GsonUtils.GSON.toJson(responseResult));
        out.flush();
    }

    /**
     * .
     *
     * @return userService
     */
    public UserService getUserService() {

        return userService;
    }

    /**
     * .
     *
     * @param userService to set
     */
    public void setUserService(final UserService userService) {

        this.userService = userService;
    }

    /**
     * .
     *
     * @return authService
     */
    public AuthService getAuthService() {

        return authService;
    }

    /**
     * .
     *
     * @param authService to set
     */
    public void setAuthService(final AuthService authService) {

        this.authService = authService;
    }

}
